#pragma once

#include "thread.h"
#include "event.h"
#include <cassert>

namespace mswin {

//it's a thread in which reports every time when it does something and when it waits for new orders
class synchronizable_thread : public thread
{
public:
	synchronizable_thread();
	void repeat(); ///< wakes the thread, if multiple repeat() are called while doit() is not finished they act as one repeat()
	bool idle() const; ///< indicates that work thread is asleep and only awaiting new orders
	void wait_idle() const; ///< waits while work thread completes all its orders and is asleep
	///returns whenever the thread is idle or GUI event happened
	///on idle return true otherwise false
	///\param dispatch_msg order the method to disptach GUI events before return
	bool idle_or_wnd_msg(bool dispatch_msg) const;
	///same as wait_idle() but also processes incoming GUI events (to call from GUI thread)
	void wait_idle_with_msg_processing() const;
protected:
	void start(); ///< last child should call StartThread() in its ctor
	void terminate(); ///< last child should call TerminateThread() in its dtor
	virtual void doit() = 0; ///< this method will be called from work thread's run()
private:
	virtual void run(); ///< do not call it, only overload
	using thread::wait_finish;
private:
	mswin::event new_order_; ///< set in main thread when something has to be done, reset in work thread when the work is done
	volatile bool terminating_; ///< signals (in destructor) to work thread to exit
	event idle_; ///< set in work thread when the work is done
};

inline synchronizable_thread::synchronizable_thread() : new_order_(false), idle_(false) {
	restart_on_error(true);
}

inline void synchronizable_thread::repeat() {
	idle_.reset();
	new_order_.set();
}

inline bool synchronizable_thread::idle() const 
	{ return idle_.is_set(); }

inline void synchronizable_thread::wait_idle() const 
	{ idle_.wait(); }

inline bool synchronizable_thread::idle_or_wnd_msg(bool dispatch_msg) const
{
	MsgWaitForMultipleObjects(1, 
		const_cast<HANDLE*>(&idle_.h), 
		FALSE, INFINITE, QS_ALLINPUT);
	if (dispatch_msg) {
		MSG msg;
		while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
			TranslateMessage(&msg); 
			DispatchMessage(&msg); 
		}
	}
	return idle();
}

inline void synchronizable_thread::wait_idle_with_msg_processing() const
	{ while (!idle_or_wnd_msg(true)); }

inline void synchronizable_thread::start() { 
	terminating_ = false;
	resume();
}

inline void synchronizable_thread::terminate() {
	terminating_ = true;
	new_order_.set();
	wait_finish();
}

inline void synchronizable_thread::run() {
	for (;;) {
		if (!new_order_.is_set()) {
			idle_.set();
			new_order_.wait();
		}
		new_order_.reset();
		if (terminating_)
			break;

		idle_.reset();
		doit();
	}
	idle_.set();
}

} //namespace mswin
